package com.cloudkinto.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cloudkinto.common.constant.GlobalConstants;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import static com.cloudkinto.common.utils.OkHttpUtils.getUnsafeOkHttpClient;

/**
 * @auther: hujun
 * @date: 2019/5/8 14:19
 * @email: 461214737@qq.com
 * @description: 亚马逊订单api请求工具类
 */
public class AmazonUtil {
    /***post请求地址*/
    private static final String POST_URL = GlobalConstants.AmazonURL;

    /***post请求后缀*/
    private static final String SUFFIX_POST_URL = "/Orders/2013-09-01";
    /***订单api版本*/
    private static final String VERSION = "2009-01-01";
    /****请求的Amazon方法*/
    private static final String ACTION = "SubmitFeed";
    /***签名方式*/
    private static final String ALGORITHM = "HmacSHA256";

    /****时间转换格式*/
    private static final String TIME_FORMAT_STR = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";

    /****编码格式*/
    private static final String CHARACTER_ENCODING = "UTF-8";

    private static final String SIGNATURE_VERSION = "2";
    private static final String FEEDTYPE = "_POST_ORDER_FULFILLMENT_DATA_";
    /**
     * 固定数据
     */
    private static String regionName = "us-west-2";
    private static String serviceName = "execute-api";
    /***订单中的所有商品均已发货*/
    String SHIPPED = "Shipped";
    private static final Logger logger = LoggerFactory.getLogger(AmazonUtil.class);

    /***
     * post请求通道
     * @param secretKey
     * @param urlParameters
     * @return
     * @throws Exception
     */
    public static String doPostSubmitFeed(String nowDate, String beforeDate, String afterDate, String url, Map<String, String> urlParameters, String secretKey, String requestBody) throws Exception {
        urlParameters.put("Timestamp", urlEncode(nowDate));
        urlParameters.put("Action", urlEncode("ListOrders"));
        urlParameters.put("SignatureMethod", urlEncode(ALGORITHM));
        urlParameters.put("Version", urlEncode("2013-09-01"));
        urlParameters.put("SignatureVersion", urlEncode(SIGNATURE_VERSION));

        urlParameters.put("CreatedBefore", urlEncode(beforeDate));
        urlParameters.put("CreatedAfter", urlEncode(afterDate));
        String formattedParameters = calculateStringToSignV2(urlParameters, url);
        logger.info("签名内容：{}", formattedParameters);
        String signature = sign(formattedParameters, secretKey);
        logger.info("签名signature: {}", signature);
        urlParameters.put("Signature", urlEncode(signature));
        String paramStr = sortParams(new StringBuilder(), urlParameters);
        logger.info("排序后参数：{}", paramStr);
        logger.info("url  " + url + "?" + paramStr);
        return doPost(url + "?" + paramStr, requestBody);
    }

    public static String doPostSubmitFeedItem(String nowDate, String url, Map<String, String> urlParameters, String secretKey) throws Exception {
        urlParameters.put("Timestamp", urlEncode(nowDate));
        urlParameters.put("Action", urlEncode("ListOrderItems"));
        urlParameters.put("SignatureMethod", urlEncode(ALGORITHM));
        urlParameters.put("Version", urlEncode("2013-09-01"));
        urlParameters.put("SignatureVersion", urlEncode(SIGNATURE_VERSION));


        String formattedParameters = calculateStringToSignV2(urlParameters, url);
        logger.info("签名内容：{}", formattedParameters);
        String signature = sign(formattedParameters, secretKey);
        logger.info("签名signature: {}", signature);
        urlParameters.put("Signature", urlEncode(signature));
        String paramStr = sortParams(new StringBuilder(), urlParameters);
        System.out.println("params : ================" + urlParameters.toString());
        logger.info("url  " + url + "?" + paramStr);
        return doPost(url + "?" + paramStr, "");
    }


    /**
     * signV2签名内容
     *
     * @param parameters
     * @param serviceUrl
     * @return
     * @throws SignatureException
     * @throws URISyntaxException
     */
    private static String calculateStringToSignV2(
            Map<String, String> parameters, String serviceUrl)
            throws URISyntaxException {
        URI endpoint = new URI(serviceUrl);
        StringBuilder data = new StringBuilder();
        data.append("POST\n");
        data.append(endpoint.getHost());
        data.append("\n");
        data.append(endpoint.getPath());
        data.append("\n");
        return sortParams(data, parameters);

    }

    /***
     * signV2签名方式
     * @param data
     * @param secretKey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws IllegalStateException
     * @throws UnsupportedEncodingException
     */
    public static String sign(String data, String secretKey)
            throws NoSuchAlgorithmException, InvalidKeyException,
            IllegalStateException, UnsupportedEncodingException {
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(new SecretKeySpec(secretKey.getBytes(CHARACTER_ENCODING),
                ALGORITHM));
        byte[] signature = mac.doFinal(data.getBytes(CHARACTER_ENCODING));
        String signatureBase64 = new String(Base64.encodeBase64(signature),
                CHARACTER_ENCODING);
        return signatureBase64;
    }

    /**
     * url非法字符转换
     *
     * @param rawValue
     * @return
     */
    public static String urlEncode(String rawValue) {
        String value = (rawValue == null) ? "" : rawValue;
        String encoded = null;

        try {
            encoded = URLEncoder.encode(value, CHARACTER_ENCODING)
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            System.err.println("Unknown encoding: " + CHARACTER_ENCODING);
        }

        return encoded;
    }

    /***
     * post请求
     * @param url
     * @param params
     * @return
     * @throws Exception
     */
    public static String doPost(String url, String params) throws Exception {
        OkHttpClient.Builder client = OkHttpUtils.getUnsafeOkHttpClient().newBuilder();
        client.connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);
        Request request = new Request.Builder()
                .addHeader("Content-Type", "text/xml")
                .addHeader("Accept", "Accept: text/plain, */*")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3724.8 Safari/537.36")
                .addHeader("x-amazon-user-agent", "AmazonJavascriptScratchpad/1.0 (Language=Javascript)")
                .addHeader("X-Requested-With", "XMLHttpRequest")
                .addHeader("Content-MD5", computeContentMD5Header(new ByteArrayInputStream(params.getBytes())))
                .post(RequestBody.create(MediaType.get("text/xml"), params))
                .url(url)
                .build();
        Response response = null;
        String body = "";
        try {
            response = client.build().newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return XmlUtils.xml2Json(body).toJSONString();
    }


    /**
     * =======================================SPAPI=========================================
     */


    /***
     * spGetToken
     * @param clientId
     * @param clientSecret
     * @param lwa
     * @return
     * @throws Exception
     */
    public static String spGetToken(String clientId, String clientSecret, String lwa, String sessionToken) throws Exception {
        Map parametersMap = new HashMap<>();
        if (StringUtils.isNotBlank(lwa)) {
            parametersMap.put("grant_type", "refresh_token");
            parametersMap.put("refresh_token", lwa);
        } else {
            parametersMap.put("grant_type", "client_credentials");
            parametersMap.put("scope", "sellingpartnerapi::migration");
        }
        parametersMap.put("client_id", clientId);
        parametersMap.put("client_secret", clientSecret);
        String body = spAPidoPOST("https://api.amazon.com/auth/o2/token", "", sessionToken, parametersMap, "");
        String access_token = JSONObject.parseObject(body).getString("access_token");
        String refresh_token = JSONObject.parseObject(body).getString("refresh_token");
        //todo 把数据存储在数据库
        return access_token;
    }

    /**
     * SP-API SP-API 通用POST方法
     *
     * @param serviceUrl    接口地址
     * @param accessToken   访问令牌
     * @param sessionToken  secret令牌，请求PII数据
     * @param parametersMap body参数
     * @param payLoad       参数toString
     * @return
     * @throws Exception
     */
    public static String spAPidoPOST(String serviceUrl, String accessToken, String sessionToken, Map parametersMap, String payLoad) throws Exception {
        return spAPidoPOST(serviceUrl, accessToken, sessionToken, GlobalConstants.secretSecrityKeySP, GlobalConstants.accessSecrityKeySP, parametersMap, payLoad);
    }

    /**
     * @param serviceUrl    接口地址
     * @param accessToken   访问令牌
     * @param sessionToken  secret令牌，请求PII数据
     * @param secretKeySP   secret令牌的持有人私钥
     * @param accessKeySP   secret令牌的持有人公钥
     * @param parametersMap body参数
     * @param payLoad       参数toString
     * @return
     * @throws Exception
     */
    public static String spAPidoPOST(String serviceUrl, String accessToken, String sessionToken, String secretKeySP, String accessKeySP, Map parametersMap, String payLoad) throws Exception {
        String timeStamp = getTimeStamp();
        String dateStamp = getDateStamp();
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("host", new URI(serviceUrl).getHost());
        headerMap.put("user-agent", GlobalConstants.amazonAppId + "/1.0 (Language=Java/1.8.0.221;Platform=Windows/10)");
        headerMap.put("content-type", "application/json; charset=utf-8");
        if (StringUtils.isNotBlank(accessToken))
            headerMap.put("x-amz-access-token", accessToken);
        if (StringUtils.isNotBlank(sessionToken))
            headerMap.put("x-amz-security-token", sessionToken);
        headerMap.put("x-amz-date", timeStamp);
        String sign = byteToHex(getSignatureData(getSignatureKey(secretKeySP, dateStamp,
                regionName, serviceName), getSignatureContent(timeStamp, getCredential(dateStamp, regionName, serviceName),
                SHA256Utils.getSHA256(canonicalRequestString("POST", new HashMap<>(), headerMap, serviceUrl, payLoad)))));
        System.out.println("结果：\n" + sign);
        OkHttpClient client = getUnsafeOkHttpClient();
        Request.Builder requestBuilder = createSPHeader(headerMap);
        if (StringUtils.isNotBlank(accessToken))
            requestBuilder.addHeader("Authorization", "AWS4-HMAC-SHA256 Credential=" + accessKeySP + "/" +
                    getCredential(dateStamp, regionName, serviceName)
                    + ",SignedHeaders=" + sortSignHeaderParams(headerMap) + ",Signature=" + sign);
        Request request = requestBuilder.url(serviceUrl)
                .post(RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(parametersMap)))
                .build();
        System.out.println("requestBody:" + JSON.toJSONString(parametersMap));
        Response response = null;
        String body = "";
        try {
            response = client.newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body;

    }

    private static String spAPidoSTS(String serviceUrl, String regionName, String serviceName, Map<String, Object> parametersMap, String payLoad) throws Exception {
        String timeStamp = getTimeStamp();
        String dateStamp = getDateStamp();
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("host", new URI(serviceUrl).getHost());
        headerMap.put("content-type", "application/json; charset=utf-8");
        headerMap.put("x-amz-date", timeStamp);
        if (parametersMap.size() > 0)
            serviceUrl = serviceUrl + "?" + sortParams(parametersMap);
        String sign = byteToHex(getSignatureData(getSignatureKey(GlobalConstants.secretKeySP, dateStamp,
                regionName, serviceName), getSignatureContent(timeStamp, getCredential(dateStamp, regionName, serviceName),
                SHA256Utils.getSHA256(canonicalRequestString("GET", parametersMap, headerMap, serviceUrl, payLoad)))));
        System.out.println("结果：\n" + sign);
        OkHttpClient client = getUnsafeOkHttpClient();
        Request.Builder requestBuilder = createSPHeader(headerMap);
        requestBuilder.addHeader("Authorization", "AWS4-HMAC-SHA256 Credential=" + GlobalConstants.accessKeySP + "/" +
                getCredential(dateStamp, regionName, serviceName)
                + ",SignedHeaders=" + sortSignHeaderParams(headerMap) + ",Signature=" + sign);
        Request request = requestBuilder.url(serviceUrl).build();
        Response response = null;
        String body = "";
        try {
            response = client.newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body;
    }

    /**
     * 构建header参数
     *
     * @param headerMap
     * @return
     */
    public static Request.Builder createSPHeader(Map<String, String> headerMap) {
        Request.Builder requestBuilder = new Request.Builder();
        Iterator<Map.Entry<String, String>> pairs =
                headerMap.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, String> data = pairs.next();
            requestBuilder.addHeader(data.getKey(), data.getValue());
        }
        return requestBuilder;
    }


    /**
     * 二进制值转换为十六进制
     *
     * @param bytes
     * @return
     */
    private static String byteToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            if ((bytes[i] & 0xff) < 0x10)
                result.append("0");
            result.append(Long.toString(bytes[i] & 0xff, 16));
        }
        return result.toString().toLowerCase();
    }

    private static String getTimeStamp() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        Calendar c = Calendar.getInstance();
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        return (dateFormat.format(c.getTime()));
    }

    private static String getDateStamp() {
        return TimeUtils.getCurrentDateFormat("yyyyMMdd");
    }






















    /**
     * 对传递参数转换
     *
     * @param data
     * @param parameters
     * @return
     */
    private static String sortParams(StringBuilder data, Map<String, String> parameters) {
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);

        Iterator<Entry<String, String>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Entry<String, String> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append(pair.getKey() + "=" + pair.getValue());
            } else {
                data.append(pair.getKey() + "=");
            }
            if (pairs.hasNext()) {
                data.append("&");
            }
        }
        return data.toString();
    }

    /**
     * Calculate content MD5 header values for feeds stored on disk.
     */
    public static String computeContentMD5HeaderValue(FileInputStream fis)
            throws IOException, NoSuchAlgorithmException {

        DigestInputStream dis = new DigestInputStream(fis,
                MessageDigest.getInstance("MD5"));

        byte[] buffer = new byte[8192];
        while (dis.read(buffer) > 0) ;

        String md5Content = new String(
                org.apache.commons.codec.binary.Base64.encodeBase64(
                        dis.getMessageDigest().digest()));

        // Effectively resets the stream to be beginning of the file
        // via a FileChannel.
        fis.getChannel().position(0);

        return md5Content;
    }

    /**
     * Consume the stream and return its Base-64 encoded MD5 checksum.
     */
    public static String computeContentMD5Header(InputStream inputStream) {
        // Consume the stream to compute the MD5 as a side effect.
        DigestInputStream s;
        try {
            s = new DigestInputStream(inputStream,
                    MessageDigest.getInstance("MD5"));
            // drain the buffer, as the digest is computed as a side-effect
            byte[] buffer = new byte[8192];
            while (s.read(buffer) > 0) ;
            return new String(
                    org.apache.commons.codec.binary.Base64.encodeBase64(
                            s.getMessageDigest().digest()),
                    "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 作用域
     *
     * @param dateStamp
     * @param regionName
     * @param serviceName
     * @return
     */
    private static String getCredential(String dateStamp, String regionName, String serviceName) {
        String credential = dateStamp + "/" + regionName + "/" + serviceName + "/aws4_request";
        System.out.println("作用域\n" + credential);
        return credential;
    }

    /**
     * 2. 构造签名内容 Algorithm + \n +
     * RequestDateTime + \n +
     * CredentialScope + \n +
     * HashedCanonicalRequest
     *
     * @param timeStamp  x-amz-date标头中以YYYYMMDD'T'HHMMSS'Z'格式指定
     * @param credential 作用域
     * @param request    hash加密过的请求
     * @return
     */
    private static String getSignatureContent(String timeStamp, String credential, String request) {
        String content = "AWS4-HMAC-SHA256" + "\n" + timeStamp + "\n" + credential + "\n" + request;
        System.out.println("签名内容\n" + content);
        return content;
    }

    /**
     * 4.签名
     *
     * @param signatureKey     签名key
     * @param signatureContent 签名内容
     * @return
     * @throws Exception
     */
    static byte[] getSignatureData(byte[] signatureKey, String signatureContent) throws Exception {
        return HmacSHA256(signatureContent, signatureKey);
    }

    static byte[] HmacSHA256(String data, byte[] key) throws Exception {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF-8"));
    }

    /**
     * 3. 构造签名key
     *
     * @param key         secret access key  AWS私有访问密钥
     * @param dateStamp   日期域
     * @param regionName  地区
     * @param serviceName 调用服务名
     * @return
     * @throws Exception
     */
    static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes();
        byte[] kDate = HmacSHA256(dateStamp, kSecret);
        byte[] kRegion = HmacSHA256(regionName, kDate);
        byte[] kService = HmacSHA256(serviceName, kRegion);
        byte[] kSigning = HmacSHA256("aws4_request", kService);
        System.out.println("签名key\n" + byteToHex(kSigning));
        return kSigning;
    }


    /**
     * 1. signV4创建规范请求
     *
     * @param requestMethod
     * @param parameters
     * @param headerParameters
     * @param serviceUrl
     * @param payLoad
     * @return
     * @throws URISyntaxException
     */
    private static String canonicalRequestString(String requestMethod,
                                                 Map<String, Object> parameters, Map<String, String> headerParameters, String serviceUrl, String payLoad)
            throws URISyntaxException {
        URI endpoint = new URI(serviceUrl);
        StringBuilder data = new StringBuilder();
        //HTTPRequestMethod
        data.append(requestMethod + "\n");
        //CanonicalURI
        data.append(endpoint.getPath());
        data.append("\n");
        // CanonicalQueryString
        data.append(sortParams(parameters));
        data.append("\n");
        //CanonicalHeaders
        data.append(sortHeaderParams(headerParameters));
        data.append("\n");
        //SignedHeaders
        data.append(sortSignHeaderParams(headerParameters));
        data.append("\n");
        //HexEncode(Hash(RequestPayload))
        data.append(SHA256Utils.getSHA256(payLoad));
//        System.out.println("payLoad\n" + payLoad);
//        data.append("2d2844666b18a9c550376b9971a2c4b773a0661fd31352fd0342ea3b8c382d5f");
        System.out.println("规范请求\n" + data.toString());
        return data.toString();
    }

    /**
     * 1.1 对传递参数转换(Body)
     *
     * @param parameters
     * @return
     */
    public static String sortParams(Map<String, Object> parameters) {
        StringBuilder data = new StringBuilder();
        if (parameters == null) return "";
        Map<String, Object> sorted = new TreeMap<String, Object>();
        sorted.putAll(parameters);
        Iterator<Map.Entry<String, Object>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, Object> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append(URLEncoder.encode(pair.getKey()) + "=" + URLEncoder.encode(pair.getValue().toString()));
            } else {
                data.append(URLEncoder.encode(pair.getKey()) + "=");
            }
            if (pairs.hasNext()) {
                data.append("&");
            }
        }
        System.out.println("参数转换(Body)\n" + data.toString());
        return data.toString();
    }


    /**
     * 1.2 对传递参数转换(Header)
     *
     * @param parameters
     * @return
     */
    private static String sortHeaderParams(Map<String, String> parameters) {
        StringBuilder data = new StringBuilder();
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);
        Iterator<Map.Entry<String, String>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, String> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append(pair.getKey().toLowerCase() + ":" + pair.getValue().trim());
            } else {
                data.append(pair.getKey().toLowerCase() + ":");
            }
            data.append("\n");
        }
        System.out.println("参数转换(Header)\n" + data.toString());
        return data.toString();
    }

    /**
     * 1.3 对传递参数转换(SignHeader)
     *
     * @param parameters
     * @return
     */
    private static String sortSignHeaderParams(Map<String, String> parameters) {
        StringBuilder data = new StringBuilder();
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);
        Iterator<String> pairs =
                sorted.keySet().iterator();
        while (pairs.hasNext()) {
            String key = pairs.next();
            if (key != null) {
                data.append(key.toLowerCase());
            }
            if (pairs.hasNext()) {
                data.append(";");
            }
        }
        System.out.println("参数转换(SignHeader)\n" + data.toString());
        return data.toString();
    }

}
